#region Copyright RenGuiYou. All rights reserved.
//=====================================================
// NeatlyFrameWork
// Author:      RenGuiyou
// Feedback: 	mailto:750539605@qq.com
//=====================================================
#endregion

using System;
using UnityEngine;
using System.Collections.Generic;

namespace Neatly.UI
{
    internal class UITween : MonoBehaviour
    {
        //        public int tweenCount = 0;
        //        public int impCount = 0;
        //        public int objCount = 0;
        private static UITween m_Instance;
        public static UITween Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    GameObject go = new GameObject();
                    m_Instance = go.AddComponent<UITween>();
                    go.hideFlags = HideFlags.HideInHierarchy;
                    DontDestroyOnLoad(go);
                }
                return m_Instance;
            }
        }

        private readonly List<UITweenImp> m_TweenImps = new List<UITweenImp>();

        private readonly UITweenPool<UITweenImp> m_TweenImpPool = new UITweenPool<UITweenImp>();
        private readonly UITweenPool<TweenObject> m_TweenObjectPool = new UITweenPool<TweenObject>();

        public static UITweenImp RadarScale(NRadarChart radar, float[] radarData, float time)
        {
            TweenObject itween = TweenObject.CreateRadarScale(radar, radarData, time);
            return CreateTweenImp(itween);
        }
        
        public static UITweenImp ButtonPress(NButtonBase button, Color start, Color target, float time)
        {
            TweenObject itween = TweenObject.CreateButtonPress(button, start, target, time);
            return CreateTweenImp(itween);
        }

        public static UITweenImp SliderMove(NSlider slider, float target, float time)
        {
            TweenObject itween = TweenObject.CreateSliderMove(slider, slider.value, target, time);
            return CreateTweenImp(itween);
        }

        public static UITweenImp Scale(GameObject gameObject, Vector3 target, float time)
        {
            return Scale(gameObject, gameObject.transform.localScale, target, time);
        }

        public static UITweenImp Scale(GameObject gameObject, Vector3 start, Vector3 target, float time)
        {
            TweenObject itween = TweenObject.CreateScale(gameObject, start, target, time);
            return CreateTweenImp(itween);
        }
        
        public static UITweenImp MoveX(RectTransform transform, float targetX, float time)
        {
            var anchoredPosition = transform.anchoredPosition;
            var target = anchoredPosition;
            target.x = targetX;
            return Move(transform, anchoredPosition, target, time);
        }
        
        public static UITweenImp MoveY(RectTransform transform, float targetY, float time)
        {
            var anchoredPosition = transform.anchoredPosition;
            var target = anchoredPosition;
            target.y = targetY;
            return Move(transform, anchoredPosition, target, time);
        }
        
        public static UITweenImp Move(RectTransform transform, Vector2 target, float time)
        {
            return Move(transform, transform.anchoredPosition, target, time);
        }

        public static UITweenImp Move(RectTransform transform, Vector3 start, Vector3 target, float time)
        {
            TweenObject itween = TweenObject.CreateMove(transform, start, target, time);
            return CreateTweenImp(itween);
        }
        
        void Update()
        {
            //            tweenCount = m_TweenImps.Count;
            //            impCount = m_TweenImpPool.Count;
            //            objCount = m_TweenObjectPool.Count;
            for (int i = m_TweenImps.Count - 1; i >= 0; i--)
            {
                if (m_TweenImps[i].IsDestroy)
                {
                    m_TweenImps[i].Destroy();
                    m_TweenImps.RemoveAt(i);
                    continue;
                }
                m_TweenImps[i].Excute(Time.unscaledDeltaTime);
            }
        }

        public static TweenObject CreateTweenObject()
        {
            var tweenObject = Instance.m_TweenObjectPool.Create();
            tweenObject.Init();
            return tweenObject;
        }

        public static void ReleaseTweenObject(TweenObject obj)
        {
            if (m_Instance)
            {
                m_Instance.m_TweenObjectPool.Release(obj);
            }
        }

        private static UITweenImp CreateTweenImp(UITweenBase tweenBase)
        {
            var imp = Instance.m_TweenImpPool.Create().Init(tweenBase);
            Instance.m_TweenImps.Add(imp);
            return imp;
        }

        public static void ReleaseTweenImp(UITweenImp imp)
        {
            if (m_Instance)
            {
                m_Instance.m_TweenImpPool.Release(imp);
            }
        }

        public static void Cancel(UITweenBase tween)
        {
            Cancel(tween.gameObject, tween.tweenType);
        }

        public static void Cancel(GameObject gameObject, TweenType tweenType = TweenType.None)
        {
            if (m_Instance == null || gameObject == null) return;
            for (int i = 0; i < m_Instance.m_TweenImps.Count; i++)
            {
                m_Instance.m_TweenImps[i].CancelUnique(gameObject, tweenType);
            }
        }
    }


    internal class UITweenImp
    {
        private UITweenBase m_Tween;
        private GameObject gameObject;
        public bool IsDestroy { get; private set; }
        private Action m_CompleteAction;
        public UITweenImp Init(UITweenBase tween)
        {
            m_Tween = tween;
            gameObject = tween.gameObject;
            IsDestroy = false;
            m_CompleteAction = null;
            return this;
        }

        public UITweenImp SetOnComplete(Action action)
        {
            m_CompleteAction = action;
            return this;
        }
        
        public void Excute(float dt)
        {
            if (m_Tween.IsDestroy)
            {
                IsDestroy = true;
                return;
            }
            if (m_Tween.Excute(dt))
            {
                IsDestroy = true;
            }
        }
        public void Destroy()
        {
            if (!m_Tween.IsDestroy)
            {
                m_CompleteAction?.Invoke();
            }
            m_Tween.OnDestroy();
            UITween.ReleaseTweenImp(this);
        }

        public void CancelUnique(GameObject go, TweenType tweenType)
        {
            if (tweenType == TweenType.None)
            {
                if (gameObject == go)
                {
                    IsDestroy = true;
                }
            }
            else
            {
                if (gameObject == go && m_Tween.tweenType == tweenType)
                {
                    IsDestroy = true;
                }
            }
        }
    }

    internal class UITweenPool<T> where T : new()
    {
        private readonly List<T> m_Pool = new List<T>();
        private int m_MaxCount = 10;
        public T Create()
        {
            if (m_Pool.Count <= 0)
            {
                return new T();
            }
            T obj = m_Pool[Count - 1];
            m_Pool.RemoveAt(Count - 1);
            return obj;
        }

        public void SetMaxCount(int count)
        {
            m_MaxCount = count;
        }

        public void Release(T obj)
        {
            if (Count >= m_MaxCount) return;
            m_Pool.Add(obj);
        }

        public int Count { get { return m_Pool.Count; } }
    }

    internal abstract class UITweenBase
    {
        public TweenType tweenType;
        public RectTransform rectTransform;
        public GameObject gameObject;
        public float m_AccumTime;
        public float m_TotalTime;
        public virtual bool IsDestroy { get { return gameObject == null; } }
        public virtual void OnDestroy() { }
        public abstract bool Excute(float dt);
        public abstract void Init();
    }

    enum TweenType
    {
        None,
        Move,
        Scale,
        ButtonPress,
        SliderMove,
        RadarScale,
    }

    internal sealed class TweenObject : UITweenBase
    {
        public override void Init()
        {
            m_Button = null;
        }

        private NRadarChart m_Radar;
        private float[] m_RadarData;
        
        private NButtonBase m_Button;
        private NSlider m_Slider;
        private float m_StartFloat;
        private float m_TargetFloat;
        private Color m_StartColor;
        private Color m_TargetColor;
        private Vector3 m_StartV3;
        private Vector3 m_TargetV3;

        public static TweenObject CreateRadarScale(NRadarChart radar, float[] data, float totalTime)
        {
            var tween = UITween.CreateTweenObject();
            tween.gameObject = radar.gameObject;
            tween.m_Radar = radar;
            tween.m_RadarData = data;
            tween.m_AccumTime = 0;
            tween.m_TotalTime = totalTime;
            tween.tweenType = TweenType.RadarScale;
            UITween.Cancel(tween);
            return tween;
        }
        
        public static TweenObject CreateButtonPress(NButtonBase button, Color start, Color target, float totalTime)
        {
            var tween = UITween.CreateTweenObject();
            tween.gameObject = button.gameObject;
            tween.m_Button = button;
            tween.m_StartColor = start;
            tween.m_TargetColor = target;
            tween.m_TotalTime = totalTime;
            tween.m_AccumTime = 0;
            tween.tweenType = TweenType.ButtonPress;
            UITween.Cancel(tween);
            return tween;
        }

        public static TweenObject CreateSliderMove(NSlider slider, float start, float target, float totalTime)
        {
            var tween = UITween.CreateTweenObject();
            tween.gameObject = slider.gameObject;
            tween.m_Slider = slider;
            tween.m_StartFloat = start;
            tween.m_TargetFloat = target;
            tween.m_TotalTime = totalTime;
            tween.m_AccumTime = 0;
            tween.tweenType = TweenType.SliderMove;
            UITween.Cancel(tween);
            return tween;
        }

        public static TweenObject CreateScale(GameObject gameObject, Vector3 start, Vector3 target, float totalTime)
        {
            var tween = UITween.CreateTweenObject();
            tween.gameObject = gameObject;
            tween.m_StartV3 = start;
            tween.m_TargetV3 = target;
            tween.m_TotalTime = totalTime;
            tween.m_AccumTime = 0;
            tween.tweenType = TweenType.Scale;
            UITween.Cancel(tween);
            return tween;
        }

        public static TweenObject CreateMove(RectTransform rectTransform, Vector3 start, Vector3 target, float totalTime)
        {
            var tween = UITween.CreateTweenObject();
            tween.rectTransform = rectTransform;
            tween.gameObject = rectTransform.gameObject;
            tween.m_StartV3 = start;
            tween.m_TargetV3 = target;
            tween.m_TotalTime = totalTime;
            tween.m_AccumTime = 0;
            tween.tweenType = TweenType.Move;
            UITween.Cancel(tween);
            return tween;
        }
        
        public override bool Excute(float dt)
        {
            m_AccumTime += dt;
            switch (tweenType)
            {
                case TweenType.Move:
                    rectTransform.anchoredPosition = Vector2.Lerp(m_StartV3, m_TargetV3, m_AccumTime / m_TotalTime);
                    break;
                case TweenType.ButtonPress:
                    m_Button.SetRenderColorGroup(Color.Lerp(m_StartColor, m_TargetColor, m_AccumTime / m_TotalTime));
                    break;
                case TweenType.Scale:
                    gameObject.transform.localScale = Vector3.Lerp(m_StartV3, m_TargetV3, m_AccumTime / m_TotalTime);
                    break;
                case TweenType.SliderMove:
                    m_Slider.SetValue(Mathf.Lerp(m_StartFloat, m_TargetFloat, m_AccumTime / m_TotalTime));
                    break;
                case TweenType.RadarScale:
                    var current = m_Radar.GetCurrentList();
                    current.Clear();
                    for (var i = 0; i < m_RadarData.Length; i++)
                    {
                        current.Add(NMathf.Lerp(0, m_RadarData[i], m_AccumTime / m_TotalTime));
                    }
                    break;
            }
            return m_AccumTime >= m_TotalTime;
        }

        public override void OnDestroy()
        {
            m_Button = null;
            UITween.ReleaseTweenObject(this);
        }
    }
}
